﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Text;
using Gallio.Runtime.Logging;
using Quail;
using Quail.DbLogger.Task;
using Quail.Helpers;

namespace Quail.DbLogger
{
    /// <summary>
    /// A simple DbLogger using the Quail database
    /// </summary>
    public class QuailDbLogger : ITaskFactory
    {
        private static readonly MailMessage Mail = new MailMessage();
        private static readonly List<QuailDbLoggerTask> RootTasks = new List<QuailDbLoggerTask>();
        private static readonly List<string> AssemblyNames = new List<string>();
        private static readonly List<string> EnvironmentNames = new List<string>();
        private static bool _sendEmailWhenTestCompletes;
        private readonly string _libraryName, _className;
        private readonly IQuailEnvironment _quailEnvironment;
        private QuailDbLoggerThread _threadInstance = new QuailDbLoggerThread();

        /// <summary>
        /// Initializes a new instance of the <see cref="QuailDbLogger"/> class.
        /// </summary>
        /// <param name="libraryName">Name of the dll library executing the test.</param>
        /// <param name="className">The classname of the test method (GetType().ToString()).</param>
        /// <param name="quailEnvironment">The IQuailEnvironment Implementation.</param>
        public QuailDbLogger(string libraryName, string className, IQuailEnvironment quailEnvironment)
        {
            _libraryName = libraryName;
            _className = className;
            _quailEnvironment = quailEnvironment;

            lock (EnvironmentNames)
            {
                if (!EnvironmentNames.Contains(quailEnvironment.GetEnvironmentName()))
                    EnvironmentNames.Add(quailEnvironment.GetEnvironmentName());
            }
        }

        #region ITaskFactory Members

        /// <summary>
        /// Gets a value indicating whether the logger supports screenshots
        /// </summary>
        /// <value></value>
        public bool SupportsScreenshot
        {
            get { return false; }
        }

        public ITaskImplementor CreateRootTask(TestSession session, string taskname)
        {
            var r = new QuailDbLoggerTask(session, _libraryName, taskname, _className,
                                         _quailEnvironment.GetEnvironmentName());
            lock (RootTasks)
                RootTasks.Add(r);

            return r;
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_threadInstance != null)
                {
                    _threadInstance.Dispose();
                    _threadInstance = null;
                }
            }
        }

        #endregion

        public bool SendEmailWhenRootTaskFails
        {
            get; set;
        }

        public static bool SendEmailWhenTestCompletes
        {
            get
            {
                return _sendEmailWhenTestCompletes;
            }

            set
            {
                if (_sendEmailWhenTestCompletes == value)
                    return;

                if (value)
                    QuailAssemblyTestFixture.OnOverallTestsFinished += OnTestsFinished;
                else
                    QuailAssemblyTestFixture.OnOverallTestsFinished -= OnTestsFinished;

                _sendEmailWhenTestCompletes = value;
            }
        }

        public static MailMessage Email
        {
            get
            {
                return Mail;
            }
        }

        public static string EmailServer
        {
            get; set;
        }

        public static string EmailStyle
        {
            get; set;
        }

        public static void AddAssembly(object assemblyobject)
        {
            var t = assemblyobject.GetType();
            var name = t.Module.Assembly.GetName().Name;

            lock (AssemblyNames)
            {
                if (!AssemblyNames.Contains(name))
                    AssemblyNames.Add(name);
            }
        }

        private static void OnTestsFinished(DateTime started, DateTime finished)
        {
            try
            {
                if (Mail.From == null ||
                    Mail.To == null ||
                    Mail.To.Count == 0 ||
                    string.IsNullOrEmpty(EmailServer))
                {
                    Gallio.Runtime.RuntimeAccessor.Logger.Log(LogSeverity.Info, "OnTestsFinished - no email being sent");
                    return;
                }

                var alltasks = new List<QuailDbLoggerTask>();

                lock (RootTasks)
                {
                    alltasks.AddRange(RootTasks);
                    RootTasks.Clear();
                }

                Mail.IsBodyHtml = true;

                var sb = new StringBuilder();
                sb.Append("<html>");

                if (!string.IsNullOrEmpty(EmailStyle))
                    sb.Append(EmailStyle);

                sb.Append("<body>");

                var assemblies = new List<string>();

                lock (AssemblyNames)
                {
                    assemblies.AddRange(AssemblyNames);
                    AssemblyNames.Clear();
                }

                var passed = alltasks
                    .Where(t => t.FinishState == LogStatus.Pass || t.FinishState == LogStatus.Information)
                    .OrderBy(t => t.TaskName)
                    .ToList();
                var failed = alltasks
                    .Where(t => t.FinishState.IsFailingStatus())
                    .OrderBy(t => t.TaskName)
                    .ToList();
                var warning = alltasks
                    .Where(t => t.FinishState == LogStatus.Warning)
                    .OrderBy(t => t.TaskName)
                    .ToList();
                var abandoned = alltasks
                    .Where(t => t.FinishState == LogStatus.Abandoned)
                    .OrderBy(t => t.TaskName).
                    ToList();
                var unexpected = alltasks
                    .Where(t => t.FinishState != LogStatus.Pass
                                && t.FinishState != LogStatus.Information
                                && !t.FinishState.IsFailingStatus()
                                && t.FinishState != LogStatus.Warning
                                && t.FinishState != LogStatus.Abandoned)
                    .OrderBy(t => t.TaskName)
                    .ToList();

                if (string.IsNullOrEmpty(Mail.Subject))
                {
                    var sub = "Quail Test Results";

                    if (EnvironmentNames.Count == 1)
                        sub += ", " + EnvironmentNames[0];

                    if (assemblies.Count == 1)
                        sub += ", " + assemblies[0];

                    sub += " - ";

                    if (failed.Count == 0 && unexpected.Count == 0 && abandoned.Count == 0)
                        sub += "success!";
                    else if (failed.Count > 0 && passed.Count > 0)
                        sub += failed.Count + " failures.";
                    else if (failed.Count > 0)
                        sub += "complete and utter failure!";
                    else
                        sub += "mixed results.";

                    Mail.Subject = sub;
                }

                sb.AppendLine("<div id=\"#mail\">");
                sb.AppendLine("<table id=\"#results\">");
                sb.AppendLine("<tr><th colspan=2>Test Completed</th></tr>");
                sb.AppendLine("<tr><td>Started:</td><td>" + started + "</td></tr>");
                sb.AppendLine("<tr><td>Finished:</td><td>" + finished + "</td></tr>");
                sb.AppendLine("<tr><td>Duration:</td><td>" + (finished - started).ToString(@"hh\:mm\:ss") +
                              "</td></tr>");
                sb.AppendLine("<tr><td>Total tests:</td><td>" + alltasks.Count + "</td></tr>");
                sb.AppendLine("<tr><td>Passed:</td><td>" + passed.Count + "</td></tr>");
                sb.AppendLine("<tr><td>Failed:</td><td>" + failed.Count + "</td></tr>");
                sb.AppendLine("<tr><td>Warning:</td><td>" + warning.Count + "</td></tr>");
                if (abandoned.Count > 0)
                    sb.AppendLine("<tr><td>Abandoned:</td><td>" + abandoned.Count + "</td></tr>");
                if (unexpected.Count > 0)
                    sb.AppendLine("<tr><td>Unexpected result state:</td><td>" + unexpected.Count + "</td></tr>");
                sb.AppendLine("</table>");

                Action<string, string, List<QuailDbLoggerTask>> buildTable =
                    (title, id, list) =>
                        {
                            if (list.Count == 0)
                                return;

                            sb.AppendLine("<br /><table id=\"result\"" + id + "\">");
                            sb.AppendLine("<tr><th colspan=3>" + title + "</th></tr>");

                            list
                                .ForEach(
                                    t =>
                                        {
                                            Func<LogStatus, bool> lestoget =
                                                status =>
                                                status.IsFailingStatus() ||
                                                status == LogStatus.Abandoned ||
                                                status == LogStatus.Warning;

                                            Func<string, string>
                                                removeExceptionDetail =
                                                    exmsg =>
                                                        {
                                                            if (exmsg.StartsWith("<h3>Exception: "))
                                                            {
                                                                var idx = exmsg.IndexOf("</h3>");

                                                                if (idx >= 0)
                                                                    return exmsg.Substring(4, idx - 4);
                                                            }

                                                            return exmsg;
                                                        };

                                            Func<string, string>
                                                shortenit =
                                                    amsg => amsg.Length > 200 ? amsg.Substring(0, 200) : amsg;


                                            var les = t.GetAllLogEntries(le => lestoget(le.Status))
                                                .ToList()
                                                .OrderByDescending(le => le.InsertDate)
                                                .Select(
                                                    le =>
                                                    new
                                                        {
                                                            le.Status,
                                                            Message = shortenit(removeExceptionDetail(le.Message)),
                                                            le.Url
                                                        })
                                                .Distinct(le => le.Message + ":" + le.Url)
                                                // .Take(5)
                                                .ToList();

                                            var count = les.Count;
                                            var first = true;
                                            var task = Http.HtmlEncode(t.ClassName + "." + t.TaskName);
                                            var url = t.AsDashboardUrl;
                                            if (url == null)
                                                task += " in Test Run #" + t.TestRunTaskId;
                                            else
                                                task = "<a href=\"" + url + "\">" + task + "</a>";

                                            if (les.Count == 0)
                                                sb.AppendLine("<tr><td>" + task +
                                                              "</td><td>" + Http.HtmlEncode(t.TaskName) +
                                                              "</td></tr>");

                                            foreach (var le in les)
                                            {
                                                var msg = Http.HtmlEncode(le.Status + ": " + le.Message);

                                                var msgurl = le.Url ?? string.Empty;

                                                if (first)
                                                {
                                                    sb.AppendLine("<tr><td rowspan=" + count + ">" + task +
                                                                  "</td><td>" + msg + "</td><td>" + msgurl + "</td></tr>");
                                                    first = false;

                                                }
                                                else
                                                {
                                                    sb.AppendLine("<tr><td>" + msg + "</td><td>" + msgurl + "</td></tr>");
                                                }
                                            }
                                        });
                            sb.AppendLine("</table>");
                        };
                buildTable("Failures", "failed", failed);
                buildTable("Warnings", "warning", warning);
                buildTable("Abandoned Tests", "abandoned", abandoned);
                buildTable("Unexpected Result Tests", "unexpected", unexpected);

                if (assemblies.Count == 0 || assemblies.Count > 1)
                {
                    sb.AppendLine("<br /><table id=\"#assemblies\">");
                    sb.AppendLine("<tr><th>Assemblies Tested</th></tr>");
                    if (assemblies.Count == 0)
                        sb.AppendLine("<tr><td>None!</td></tr>");
                    else
                        assemblies.ForEach(a => sb.AppendLine("<tr><td>" + Http.HtmlEncode(a) + "</td></tr>"));
                    sb.AppendLine("</table>");
                }

                sb.AppendLine("</div>");
                sb.Append("</body></html>");
                Mail.Body = sb.ToString();
                EmailServer
                    .Split(';')
                    .Where(s => !string.IsNullOrEmpty(s))
                    .ToList()
                    .ForEach(s =>
                                 {
                                     Gallio.Runtime.RuntimeAccessor.Logger.Log(LogSeverity.Info, "Test completion email sent to " + s);
                                     var client = new SmtpClient(s);
                                     client.Send(Mail);
                                 });

                
            }
            catch (Exception e)
            {
                // absorb/ignore exceptions
                Gallio.Runtime.RuntimeAccessor.Logger.Log(LogSeverity.Warning, "Error during OnTestsFinished: " + e.Message);
            }
        }
    }
}
